using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SynapseGaming.SunBurn.Framework.Content;
using SynapseGaming.SunBurn.Framework.Core;
using SynapseGaming.SunBurn.Framework.Graphics;
using SynapseGaming.SunBurn.Framework.Input;
using SynapseGaming.SunBurn.Framework.Models;
using SynapseGaming.SunBurn.Framework.Primitives;


namespace GameLibrary
{
    public enum ToolType
    {
        Raise,
        Lower,
        Smooth,
        Precision,
        Paint,
        Flatten
    }

	/// <summary>
    /// Sample screen for the game, which can render in-game scenes, ui, or similar.
    /// </summary>
    public class SimpleGameScreen : GameScreen
    {
        public HeightMap HeightMap = new HeightMap(10.0f);
        public ToolType ToolType
        {
            get { return toolType; }
            set
            {
                toolType = value;
                HeightMap.DrawCursor = (toolType != GameLibrary.ToolType.Precision);
            }
        }
        public int TextureIndex;

        private ToolType toolType = ToolType.Raise;

        private bool flatteningTerrain;

        private Camera camera;
        private Matrix projection;

        private bool wasLeftButtonPressed;

        public bool IsActive = true;

        private Vector3 vertexPosition;
        private float cachedHeight;
        private Vector2 cachedMousePosition;
        private bool isMovingVertex;

		/// <summary>
        /// Called when the program switches to the screen. This is
        /// where screen assets are loaded and resources are created.
        /// </summary>
        public override void LoadContent()
        {
            camera = new Camera();
            DebugShapeRenderer.Initialize(Platform.Instance.GraphicsDevice);
        }

		/// <summary>
        /// Called when the program switches away from this screen
        /// and is where screen resources are disposed.
        /// </summary>
        public override void UnloadContent()
        {
        }

        public Ray GetMouseRay()
        {
            int mouseX = Platform.Instance.InputDevice.MouseState.X;
            int mouseY = Platform.Instance.InputDevice.MouseState.Y;

            Vector2 position = new Vector2(mouseX, mouseY);

            Vector3 near = new Vector3(position, 0f);
            Vector3 far = new Vector3(position, 1f);

            Matrix translation = Matrix.CreateTranslation(Vector3.Zero);

            near = Platform.Instance.GraphicsDevice.Viewport.Unproject(near, projection, camera.View, translation);
            far = Platform.Instance.GraphicsDevice.Viewport.Unproject(far, projection, camera.View, translation);

            Ray pickRay = new Ray(near, Vector3.Normalize(far - near));

            return pickRay;
        }

		/// <summary>
        /// Allows the screen to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides the time elapsed since the last screen draw / update.</param>
        public override void Update(GameTime gameTime)
        {
            InputDevice input = Platform.Instance.InputDevice;

            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 1024.0f / 720.0f, 1.0f, 10000.0f);

            camera.Update((float)gameTime.ElapsedGameTime.TotalSeconds);

            Ray mouseRay = GetMouseRay();
            MouseState mouseState = Platform.Instance.InputDevice.MouseState;

            Vector2 mouseDifference = new Vector2(mouseState.X, mouseState.Y) - cachedMousePosition;

            Triangle triangle;
            float? rayLength = HeightMap.QuadTree.Intersects(ref mouseRay, out triangle);

            if (rayLength.HasValue || isMovingVertex)
            {
                Vector3 rayTarget = Vector3.Zero;

                if (rayLength.HasValue)
                {
                    rayTarget = mouseRay.Position + mouseRay.Direction * rayLength.Value;

                    HeightMap.groundCursorPosition.X = ((rayTarget.X / HeightMap.CellSize) *
                        HeightMap.CellSize) / (HeightMap.Size * HeightMap.CellSize);

                    HeightMap.groundCursorPosition.Y = rayTarget.Y;

                    HeightMap.groundCursorPosition.Z = ((rayTarget.Z / HeightMap.CellSize) *
                        HeightMap.CellSize) / (HeightMap.Size * HeightMap.CellSize);

                    if (ToolType == GameLibrary.ToolType.Precision && isMovingVertex == false)
                    {
                        DebugShapeRenderer.AddBoundingSphere(new BoundingSphere(new Vector3(
                            (int)(rayTarget.X / HeightMap.CellSize + 0.5f) * HeightMap.CellSize, 
                            rayTarget.Y, (int)(rayTarget.Z / HeightMap.CellSize + 0.5f) * HeightMap.CellSize), HeightMap.CellSize / 2.0f), Colors.Red);
                    }
                }

                Point pixel = new Point((int)(rayTarget.X / HeightMap.CellSize),
                                        (int)(rayTarget.Z / HeightMap.CellSize));

                if (mouseState.LeftButtonPressed && IsActive)
                {
                    switch (ToolType)
                    {
                        case (ToolType.Raise):
                            {
                                HeightMap.RaiseHeight();
                                break;
                            }
                        case (ToolType.Lower):
                            {
                                HeightMap.LowerHeight();
                                break;
                            }
                        case (ToolType.Smooth):
                            {
                                HeightMap.Smooth();
                                break;
                            }
                        case (ToolType.Precision):
                            {
                                isMovingVertex = true;

                                if (wasLeftButtonPressed == false)
                                {
                                    vertexPosition = new Vector3((int)(rayTarget.X / HeightMap.CellSize + 0.5f),
                                                                 (int)(rayTarget.Y / HeightMap.CellSize),
                                                                 (int)(rayTarget.Z / HeightMap.CellSize + 0.5f)) * HeightMap.CellSize;

                                    cachedHeight = HeightMap.GetGroundHeight(vertexPosition);
                                }

                                float height = HeightMap.GetGroundHeight(vertexPosition);
                                vertexPosition.Y = height;

                                DebugShapeRenderer.AddBoundingSphere(new BoundingSphere(
                                    new Vector3(vertexPosition.X, cachedHeight, vertexPosition.Z), HeightMap.CellSize / 2.0f), Colors.Red);

                                Matrix translation = Matrix.CreateTranslation(vertexPosition);

                                GraphicsDevice graphicsDevice = Platform.Instance.GraphicsDevice;

                                Vector3 startPositionScreen = graphicsDevice.Viewport.Project(
                                    vertexPosition, projection, camera.View, Matrix.Identity);
                                Vector3 endPositionScreen = graphicsDevice.Viewport.Project(
                                    vertexPosition + Vector3.Up, projection, camera.View, Matrix.Identity);

                                Vector3 screenDirection = Vector3.Normalize(endPositionScreen - startPositionScreen);

                                float amount = Vector3.Dot(screenDirection, new Vector3(mouseDifference, startPositionScreen.Z));

                                Vector3 startPositionWorld = graphicsDevice.Viewport.Unproject(startPositionScreen,
                                    projection, camera.View, Matrix.Identity);

                                Vector3 endPositionWorld = graphicsDevice.Viewport.Unproject(startPositionScreen + screenDirection * amount,
                                    projection, camera.View, Matrix.Identity);

                                cachedHeight += endPositionWorld.Y - startPositionWorld.Y;
                                HeightMap.MoveVertices(vertexPosition, cachedHeight);

                                break;
                            }
                        case (ToolType.Flatten):
                            {
                                if (flatteningTerrain == false)
                                {
                                    HeightMap.FlattenHeight = HeightMap.groundCursorPosition.Y;
                                    flatteningTerrain = true;
                                }
                                HeightMap.FlattenVertices();
                                break;
                            }
                        case (ToolType.Paint):
                            {
                                HeightMap.Paint(pixel.X, pixel.Y, TextureIndex);
                                break;
                            }
                    }
                }
                else
                {
                    flatteningTerrain = false;
                }

                if (mouseState.LeftButtonPressed == false &&
                    wasLeftButtonPressed)
                {
                    isMovingVertex = false;

                    HeightMap.UpdateHeightTexture();
                    HeightMap.UpdateColorTexture(true);
                }
            }

            cachedMousePosition = new Vector2(mouseState.X, mouseState.Y);
            wasLeftButtonPressed = mouseState.LeftButtonPressed;
            HeightMap.Update();
        }

		/// <summary>
        /// Allows the screen to render.
        /// </summary>
        /// <param name="gameTime">Provides the time elapsed since the last screen draw / update.</param>
        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice device = Platform.Instance.GraphicsDevice;

            device.Clear(Colors.CornflowerBlue);

            HeightMap.Draw(camera.View, projection);

            DebugShapeRenderer.Draw(gameTime, camera.View, projection);
        }
    }
}
